fscanf в C/C++: считывание из файла

Привет! В этой статье мы поговорим о функции fscanf. Эта функция очень похожа на scanf с тем отличием, что она считывает значения не из стандартного ввода, а из файла. Начнем мы с обзора этой функции на примерах, а потом посмотрим на спецификаторы формата, которые эта функция поддерживает (они не отличаются от scanf, поэтому знание этой функции будет большим плюсом). В конце есть упражнения для закрепления материала.

Иллюстрация fscanf

Как считать значения из файла с помощью fscanf

Для считывания значений из файла можно воспользоваться функцией fscanf. Эта функция объявлена в заголовочном файле <stdio.h>. В C++ вы можете подключить <cstdio>. Выглядит эта функция вот так:

int fscanf ( FILE * stream, const char * format, ... );
  • Первым аргументом нужно передать указатель на файл, из которого нужно производить считывание.
  • Вторым аргументом нужно передать строку формата, которая определяет как нужно читать файл.
  • Далее нужно передать столько аргументов, сколько fscanf будет ожидать в зависимости от строки формата.
  • Функция возвращает количество значений, которые удалось считать из файла. Если все прошло без ошибок, то это значение должно быть равно количеству аргументов после строки формата.

Давайте посмотрим на несколько примеров использования этой функции:

#include <stdio.h>

int main() {
  FILE * f = fopen("input.txt", "r");
  int x;
  int n = fscanf(f, "x: %d", &x);
  fclose(f);

  printf("x = %d, n = %d", x, n);

  return 0;
}

input.txt:

x: 4

Вывод программы:

x = 4, n = 1

В программе выше мы считали переменную типа int из файла, используя спецификатор формата %d (подробнее о спецификаторах ниже). После считывания, мы вывели x и количество считанных аргументов.

Давайте теперь попробуем считать что-то посложнее:

#include <stdio.h>

int main() {
  FILE* f = fopen("input.txt", "r");
  unsigned int x;
  char s[100];
  float pi;
  int n = fscanf(f, "hex: %x, s = %s\nfloat <- %f", &x, s, &pi);
  fclose(f);

  printf("x = %d, s = %s, pi = %f, n = %d", x, s, pi, n);

  return 0;
}

input.txt:

hex: 0xABC, s = fscanf
float <- 3.14

Вывод программы:

x = 2748, s = fscanf, pi = 3.140000, n = 3

Как вы можете видеть, fscanf справился со сложным форматом. Давайте теперь “испортим” содержимое файла input.txt, чтобы fscanf смог считать только первые два значения:

hex: 0xABC, s = fscanf
UNKNOWN <- 3.14

Тут мы поменяли начало второй строки. Давайте теперь запустим программу выше и посмотрим на ее вывод:

x = 2748, s = fscanf, pi = 0.000000, n = 2

Тут все ожидаемо:

  • fscanf смог прочитать первые два значения, поскольку тут ничего не поменялось.
  • Но при считывании второй строки файла, fscanf уже встречает неожиданный символ U. На этом считывание заканчивается.
  • В итоге мы видим, что только два значение было считано (n равно двум). pi остался со значением по умолчанию (в данном случае нулем).

Что будет выведено на экран?

#include <stdio.h>

int main() {
  FILE* f = fopen("data.txt", "r");
  char word[50];
  double number;
  int n = fscanf(f, "%s = %lf", word, &number);
  fclose(f);

  printf("word = %s, number = %lf, n = %d", word, number, n);

  return 0;
}

data.txt:

result = 42.58
word = result, number = 42.580000, n = 2
word = 42.58, number = 0.000000, n = 1
word = result, number = 42.580000, n = 1
word = result, number = 0.000000, n = 2

Давайте теперь посмотрим на все возможные спецификаторы формата, которые можно использовать с функцией fscanf.

Спецификаторы формата

Структура спецификатора в fscanf немного отличается от спецификатора в fprintf (в квадратных скобках опциональные модификаторы):

%[*][ширина][размер]тип

Как видите, обязательными являются только знак процента и тип; остальные можно не использовать. Сами спецификаторы никак не отличаются от спецификаторов функции scanf (той, которая читает из стандартного ввода, а не из файла).

Тип

ТипОписаниеПримерФайл
i

Считывает целое число в восьмеричной, десятеричной или шестнадцатеричной системе.

fscanf(f, "%i", &x); // x = 987
fscanf(f, "%i", &x); // x = -987
fscanf(f, "%i", &x); // x = 987

01733
-987
0x3db

d

Считывает целое число в десятеричной системе.

fscanf(f, "%d", &x); // x = -987

-987

u

Считывает целое положительное число в десятеричной системе.

fscanf(f, "%u", &x); // x = 987

987

o

Считывает целое число в восьмеричной системе.

fscanf(f, "%o", &x); // x = -987

-1733

x

Считывает целое число в шестнадцатеричной системе.

fscanf(f, "%x", &x); // x = -987
fscanf(f, "%x", &x); // x = 987

-3db
0x3db

f, e, g, a

Считывает вещественное число.

fscanf(f, "%f", &x); // x = 12.34
fscanf(f, "%f", &x); // x = 12.34
fscanf(f, "%f", &x); // x = 12.34
fscanf(f, "%f", &x); // x = 12.34

1.234e+01
12.34
987
0x1.8ap+3

c

Считывает символ.

fscanf(f, "%c", &x); // x = 'C'

C

s

Считывает строку до первого пробельного символа.

fscanf(f, "%s", &x); // x = "C++"

C++

p

Считывает адрес в памяти.

fscanf(f, "%p", &x); // x = 0x16dc

0x16dc

[<символы>]

Считывает строку до тех пор, пока не встретит символ, который отличается от символов, внутри квадратных скобок.

fscanf(f, "%[1a]", &x); // x = "aa1"

aa1398

[^<символы>]

Считывает строку до тех пор, пока не встретит символ, который совпадает с одним из символов, внутри квадратных скобок, после ^.

fscanf(f, "%[^a1]", &x); // x = "C++"

C++11

n

Ничего не считывает. Записывает количество считанных символов до этого момента.

fscanf(f, "C++%n11", &x); // x = 3

C++11

%%

Считывает знак процента.

fscanf(f, "%%%c", &x); // x = 'd'

%d

Игнорирование ввода

Мы можем указать звездочку после процента, чтобы не сохранять прочитанное значение:

FILE * f = fopen("input.txt", "r");
int x;
fscanf(f, "Пропустить: %*d, сохранить: %d", &x);
printf("Число: %d", x);

input.txt:

Пропустить: 123, сохранить: 456

Пример вывода:

Число: 456

Звездочку можно использовать с любым типом из таблицы выше, не только с числами.

Ширина

Мы можем указать максимальное количество символов, которое будет прочитано функцией fscanf при считывании значения:

FILE * f = fopen("input.txt", "r");
char s[4]; // 3 символа + '\0'
fscanf(f, "%3s", s);
printf("Считали: %s", s);

input.txt:

C++11

Вывод:

Считали: C++

Как и звездочка, ширину можно указывать для любого типа.

Размер

По умолчанию fscanf считывает значения типа int, unsigned int, float и char (зависит от типа спецификатора). Чтобы считать, например, значение в переменную типа long long int, нам нужно явно указать размер:

#include <math.h>
#include <stdio.h>

int main() {
  FILE * f = fopen("input.txt", "r");
  long long int x;
  printf("Число: ");
  fscanf(f, "%lld", &x);
  //      ^ добавили ll для long long int
  printf("Считали: %lld\n", x);
  //                 ^ в printf тоже не забываем

  return 0;
}

input.txt:

Число: 10000000000

Вывод:

Считали: 10000000000

Без указания размера вывод будет выглядеть примерно так:

Считали: 5705032704

Мы видим такой вывод, потому что десять миллиардов просто не влезают в тип int, который fscanf пытается считать без явного указания размера.

Вот таблица размеров, которые можно использовать:

РазмерТип
hh

signed char / unsigned char

h

short int / unsigned short int

l

long int / unsigned long int

ll

long long int / unsigned long long int

j

intmax_t / uintmax_t

z

size_t

t

ptrdiff_t

L

long double

Упражнения

  1. Считывание различных типов данных:
    Напишите программу на C++, которая использует функцию fscanf для считывания данных разных типов из файла. В файле должны быть следующие данные: целое число, вещественное число, строка. После считывания, программа должна выводить эти данные на экран.
  2. Обработка ошибок считывания:
    Измените ваш файл с данными таким образом, чтобы одна из строк не соответствовала формату, который вы ожидаете. Затем модифицируйте вашу программу так, чтобы она корректно обрабатывала этот случай (например, выводила сообщение об ошибке).
  3. Использование различных спецификаторов формата:
    Напишите программу, которая демонстрирует использование разных спецификаторов формата с функцией fscanf. Создайте файл с различными данными (например, целыми числами, вещественными числами, строками, шестнадцатеричными числами) и считайте их из файла, выводя результат на экран.

Обсуждение